/*
 * SAXModifierReader.java
 *
 * Created on February 24, 2006, 2:16 PM
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package org.interaction3d.assembly.sax;


import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Locale;
import java.util.Scanner;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.interaction3d.assembly.Mesh;
import org.interaction3d.assembly.Modifier;
import static javax.xml.XMLConstants.*;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

public class SAXModifierReader
{
    // <editor-fold defaultstate="collapsed" desc=" MeshHandler ">

    private static class ModifierHandler extends DefaultHandler
    {
        private final Modifier modifier;

        private int vertices;
        // coordinates
        private String name;
        private int dimension, split, count;
        //mapping
        private int[][] idMap;
        private int index, numMaps, selected;


        private Locator locator;
        private AttributeParser ap;
        private boolean readCData = false;
        private StringBuilder cdata = new StringBuilder();

        public ModifierHandler(Modifier modifier)
        {
            if(modifier == null)
            {
                throw new NullPointerException();
            }

            this.modifier = modifier;
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException
        {
            if (readCData)
                this.cdata.append(ch, start, length);
        }

        @Override
        public void setDocumentLocator(Locator locator)
        {
            this.ap = new AttributeParser(locator);
            this.locator = locator;
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
        {
            if (qName.equals("Mesh"))
            {

            }
            else if (qName.equals("Vertices"))
            {
                this.vertices = this.ap.parseInt(attributes, "count");
            }
            else if (qName.equals("Elements"))
            {
                final int numVertexElems = this.ap.parseInt(attributes, "count");
                idMap = new int[numVertexElems][2];
                index = 0; selected = 0;
                //modifier.coordinateArrays(numVertexElems);
            }
            else if (qName.equals("Element"))
            {
                this.name = this.ap.parseString(attributes, "name");
                idMap[index][1] = this.ap.parseInt(attributes, "map", -1);
            }
            else if (qName.equals("Coordinates"))
            {
                this.dimension = this.ap.parseInt(attributes, "dimension");
                this.count = this.ap.parseInt(attributes, "count");
                this.split = this.ap.parseInt(attributes, "split", count);


                boolean use = false;//modifier.coordinateArray(name, dimension, count, split);

                idMap[index++][0] = use
                                  ? selected++
                                  : -1;

                this.cdata.setLength(0);
                this.readCData = true;
            }
            else if (qName.equals("Split"))
            {
                this.readCData = false;
            }
            else if (qName.equals("Mapping"))
            {
                //modifier.vertices(this.vertices);

                this.numMaps = this.ap.parseInt(attributes, "count");

                cdata.setLength(0);
                readCData = true;
            }
            else if (qName.equals("Primitives"))
            {
                final int numGroups = this.ap.parseInt(attributes, "groups");
                //modifier.primtiveGroups(numGroups);
            }
            else if (qName.equals("Triangles"))
            {
                count = this.ap.parseInt(attributes, "count");
                String triGroupName = attributes.getValue("material");

                //modifier.primtiveGroup(triGroupName, PrimitiveType.TRIANGLE, count);

                cdata.setLength(0);
                readCData = true;
            }
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException
        {
            this.readCData = false;

            if (qName.equals("Split"))
            {
                //parseCoordinates(cdata, dimension, split, modifier);
                cdata.setLength(0);
                this.readCData = true;
            }
            else if (qName.equals("Coordinates"))
            {
                //if (count > this.split)
                //    parseCoordinateRefs(this.cdata, count-split, modifier);
                //else
                //    parseCoordinates(cdata, dimension, split, modifier);
            }
            else if (qName.equals("Mapping"))
            {
                int[] selection = new int[selected];

                for(int i=0; i<idMap.length; i++)
                {
                    int id = idMap[i][0];
                    if(id >= 0)
                    {
                        int map = idMap[i][1];
                        selection[id] = map;
                    }
                }

                //parseVertices(this.cdata, vertices, numMaps, selection, modifier);
            }
            else if (qName.equals("Triangles"))
            {
                //parseTriangles(cdata, count, modifier);
            }
        }

        @Override
        public void error(SAXParseException e)
        {
            String msg = "Error at " + locator.getLineNumber()  + ":" + locator.getColumnNumber();
            handle(e, msg);
        }

        @Override
        public void fatalError(SAXParseException e)
        {
            String msg = "Fatal error at " + locator.getLineNumber()  + ":" + locator.getColumnNumber();
            handle(e, msg);
        }

        @Override
        public void warning(SAXParseException e)
        {
            String msg = "Warning at " + locator.getLineNumber()  + ":" + locator.getColumnNumber();
            handle(e, msg);
        }
    }

    static private void parseVertices(CharSequence coordinates, int count, int elemets, int[] selection, Mesh mesh)
    {
        final Scanner scanner = new Scanner(coordinates.toString()).useLocale(Locale.ENGLISH);

        int[] allElems = new int[elemets];
        int[] selectedElems = new int[selection.length];

        for(int numMatches=0; numMatches<count; numMatches++)
        {
            for(int i=0; i<allElems.length; i++)
            {
                allElems[i] = scanner.nextInt();
            }

            for(int i=0; i<selection.length; i++)
            {
                selectedElems[i] = (selection[i] < 0)
                                 ? numMatches
                                 : allElems[selection[i]];
            }

            //mesh.vertex(selectedElems);
        }
    }


    static private void parseCoordinates(CharSequence coordinates, int dimension, int count, Mesh mesh)
    {
        final Scanner scanner = new Scanner(coordinates.toString()).useLocale(Locale.ENGLISH);


        double[] coord = new double[dimension];
        for(int numMatches=0; numMatches<count; numMatches++)
        {
            for(int i=0; i<coord.length; i++)
            {
                coord[i] = scanner.nextDouble();
            }
            //mesh.coordinate(coord);
        }
    }

    static private void parseCoordinateRefs(CharSequence coordinates, int count, Mesh mesh)
    {
        final Scanner scanner = new Scanner(coordinates.toString()).useLocale(Locale.ENGLISH);

        for(int numMatches=0; numMatches<count; numMatches++)
        {
            //mesh.coordinate(scanner.nextInt());
        }
    }

    static private void parseTriangles(CharSequence coordinates, int count, Mesh mesh)
    {
        final Scanner scanner = new Scanner(coordinates.toString()).useLocale(Locale.ENGLISH);

        int[] triangle = new int[3];
        for(int numMatches=0; numMatches<count; numMatches++)
        {
            for(int i=0; i<triangle.length; i++)
            {
                triangle[i] = scanner.nextInt();
            }
            //mesh.primtive(triangle);
        }
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc=" Variables ">

    private final SAXParser parser;

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc=" Constructors ">

    private SAXModifierReader(SAXParserFactory factory) throws ParserConfigurationException, SAXException
    {
        URL schemaURL = getClass().getResource("/org/interaction3d/assembly/Modifier.xsd");
        Schema schema = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI).newSchema(schemaURL);
        factory.setSchema(schema);
        factory.setNamespaceAware(true);//impoartant for xsd
        factory.setValidating(false);//only for dtd
        parser = factory.newSAXParser();
    }

    public SAXModifierReader() throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException
    {
        this(SAXParserFactory.newInstance());
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc=" Methods ">

    public Modifier.Assembly assembly(final String uri)
    {
        return new Modifier.Assembly()
        {
            public void assemble(Modifier modifier)
            {
                try
                {
                    parser.parse(uri, new ModifierHandler(modifier));
                }
                catch (SAXException ex)
                {
                    handle(ex);
                }
                catch (IOException ex)
                {
                    handle(ex);
                }
            }
        };
    }

    public Modifier.Assembly assembly(final InputSource is)
    {
        return new Modifier.Assembly()
        {
            public void assemble(Modifier modifier)
            {
                try
                {
                    parser.parse(is, new ModifierHandler(modifier));
                }
                catch (SAXException ex)
                {
                    handle(ex);
                }
                catch (IOException ex)
                {
                    handle(ex);
                }
            }
        };
    }

    public Modifier.Assembly assembly(final InputStream in)
    {
        return new Modifier.Assembly()
        {
            public void assemble(Modifier modifier)
            {
                try
                {
                    parser.parse(in, new ModifierHandler(modifier));
                }
                catch (SAXException ex)
                {
                    handle(ex);
                }
                catch (IOException ex)
                {
                    handle(ex);
                }
            }
        };
    }

    public Modifier.Assembly assembly(final File f)
    {
        return new Modifier.Assembly()
        {
            public void assemble(Modifier mesh)
            {
                try
                {
                    parser.parse(f, new ModifierHandler(mesh));
                }
                catch (SAXException ex)
                {
                    handle(ex);
                }
                catch (IOException ex)
                {
                    handle(ex);
                }
            }
        };
    }

    // </editor-fold>

    static private void handle(Exception ex)
    {
        handle(ex, null);
    }

    static private void handle(Exception ex, String msg)
    {
        Logger.getLogger(SAXModifierReader.class.getName()).log(Level.SEVERE, msg, ex);
        throw new RuntimeException(ex);
    }

    static
    {
        Logger logger = Logger.getLogger(SAXModifierReader.class.getName());
        logger.setLevel(Level.ALL);
        logger.addHandler(new ConsoleHandler());
    }
}


///**
// *
// * @author Michael Nischt
// * @version 0.1
// */
//public class SAXModifierReader implements Modifier {
//
//    // <editor-fold defaultstate="collapsed" desc=" BlendElement ">
//
//    private static class BlendElement extends FloatCoordinates {
//	final String name;
//	final int[] map;
//	BlendElement(String name) { this(name, new int[0]); }
//	BlendElement(String name, int[] map) { this.name = name; this.map = map; }
//	public int getBlendCount() { return map.length > 0 ? map.length : this.getCount(); }
//    }
//
//    // </editor-fold>
//
//    // <editor-fold defaultstate="collapsed" desc=" Variables ">
//
//    private BlendElement[] blendElements;
//
//    // </editor-fold>
//
//    // <editor-fold defaultstate="collapsed" desc=" Constructors ">
//
//    /** Creates a new instance of SAXModifierReader */
//    private SAXModifierReader(SAXParserFactory factory) throws ParserConfigurationException, SAXException {
//	//factory.setNamespaceAware(true);
//	//factory.setValidating(false);
//	//factory.setSchema(SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI).newSchema(getClass().getResource("/net/monoid/games/assembly/xml/validation/MorphTarget.xsd")));
//    }
//
//    /** Creates a new instance of SAXModifierReader */
//    public SAXModifierReader(File f) throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException {
//	this(SAXParserFactory.newInstance(), f);
//    }
//
//    /** Creates a new instance of SAXModifierReader */
//    public SAXModifierReader(String uri) throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException {
//	this(SAXParserFactory.newInstance(), uri);
//    }
//
//    /** Creates a new instance of SAXModifierReader */
//    public SAXModifierReader(InputStream is) throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException {
//	this(SAXParserFactory.newInstance(), is);
//    }
//
//    /** Creates a new instance of SAXModifierReader */
//    public SAXModifierReader(InputSource is) throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException {
//	this(SAXParserFactory.newInstance(), is);
//    }
//
//
//    /** Creates a new instance of SAXModifierReader */
//    public SAXModifierReader(SAXParserFactory factory, File f) throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException {
//	this(factory);
//	factory.newSAXParser().parse(f, new ModifierHandler());
//    }
//
//    /** Creates a new instance of SAXModifierReader */
//    public SAXModifierReader(SAXParserFactory factory, String uri) throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException {
//	this(factory);
//	factory.newSAXParser().parse(uri, new ModifierHandler());
//    }
//
//    /** Creates a new instance of SAXModifierReader */
//    public SAXModifierReader(SAXParserFactory factory, InputStream is) throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException {
//	this(factory);
//	factory.newSAXParser().parse(is, new ModifierHandler());
//    }
//
//    /** Creates a new instance of SAXModifierReader */
//    public SAXModifierReader(SAXParserFactory factory, InputSource is) throws ParserConfigurationException, SAXException, IllegalArgumentException, IOException {
//	this(factory);
//	factory.newSAXParser().parse(is, new ModifierHandler());
//    }
//
//    // </editor-fold>
//
//    // <editor-fold defaultstate="collapsed" desc=" Modifier ">
//
//    public int getBlendCount() {
//        return this.blendElements[0].getBlendCount();
//    }
//
//    public int getBlendElementCount() {
//        return this.blendElements.length;
//    }
//
//    public String getBlendElementName(int index) {
//        return this.blendElements[index].name;
//    }
//
//    public Coordinates getBlendElementCoordinates(int index) {
//        return this.blendElements[index];
//    }
//
//    public Coordinates getBlendElementCoordinates(String name) {
//	for(int i=0; i<this.blendElements.length; i++)
//	    if(this.blendElements[i].name.equals(name))
//		return this.blendElements[i];
//
//	return null;
//    }
//
//    public int[] getBlend(int index, int[] elements) {
//        if(elements.length < blendElements.length)
//            elements = new int[blendElements.length];
//
//	for (int i = 0; i < this.blendElements.length; i++) {
//	    final int[] map = this.blendElements[i].map;
//	    if(map.length > 0)
//		elements[i] = map[index];
//	    else
//		elements[i] = index;
//	}
//	return elements;
//    }
//
//    public int[] getBlendElements(int index, int... elements) {
//	for (int i = 0; i < elements.length; i++) {
//	    final int[] map = this.blendElements[elements[i]].map;
//	    if(map.length > 0)
//		elements[i] = map[index];
//	    else
//		elements[i] = index;
//	}
//	return elements;
//    }
//
//    // </editor-fold>
//
//    // <editor-fold defaultstate="collapsed" desc=" SAXModifierReaderHandler ">
//
//    private class ModifierHandler extends DefaultHandler {
//
//	private int index;
//	private int blendCount;
//	private boolean split;
//
//	private BlendElement blendElement;
//	private float[] coords;
//	private int[] duplicates;
//
//	private int[][] maps;
//	private int[] map;
//
//
//        private Locator locator;
//	private AttributeParser ap;
//
//	private boolean readCData = false;
//	private StringBuilder cdata = new StringBuilder();
//
//	public void characters(char[] ch, int start, int length) throws SAXException {
//	    if(readCData)
//		this.cdata.append(ch, start, length);
//	}
//
//	public void setDocumentLocator(Locator locator) {
//	    this.ap = new AttributeParser(locator);
//	    this.locator = locator;
//	}
//
//	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
//	    if(qName.equals("MorphTarget")) {
//		/* Blends */
//	    } else if(qName.equals("Blends")) {
//		this.blendCount  = this.ap.parseInt(attributes, "count");
//	    } else if(qName.equals("Maps")) {
//		final int count = this.ap.parseInt(attributes, "count");
//		this.maps = new int[count][];
//		for (int i=0; i<maps.length; i++)
//		    this.maps[i] = new int[blendCount];
//		this.index = 0;
//	    } else if(qName.equals("Map")) {
//		cdata.setLength(0);
//		readCData = true;
//		this.map = new int[this.blendCount];
//		this.maps[index++] = map;
//		/* BlendElements */
//	    } else if(qName.equals("Elements")) {
//		if(this.maps == null)
//		    this.maps = new int[0][];
//		final int count = this.ap.parseInt(attributes, "count");
//		blendElements = new BlendElement[count];
//		this.index = 0;
//	    } else if(qName.equals("Element")) {
//		final String name   = this.ap.parseString(attributes, "name");
//		final int mapIndex  = this.ap.parseInt(attributes, "map", -1);
//		if(mapIndex < 0)
//		    this.blendElement = new BlendElement(name);
//		else
//		    this.blendElement = new BlendElement(name, maps[mapIndex]);
//		blendElements[index++] = this.blendElement;
//	    } else if(qName.equals("Coordinates")) {
//		final int count     = this.ap.parseInt(attributes, "count");
//
//                if(count != this.blendCount)
//		    throw new SAXParseException("The number of unmapped blend element coordinates must match the number of blends.", this.locator);
//
//                final int dimension = this.ap.parseInt(attributes, "dimension");
//		final int split     = this.ap.parseInt(attributes, "split", count);
//		try {
//		    this.coords     = this.blendElement.wrapScalar(split, dimension);
//		    this.duplicates = this.blendElement.wrapDuplicates(count-split);
//		} catch(Exception e) {
//		    throw new SAXParseException("", this.locator); //TODO: message
//		}
//		if(this.blendElement.getBlendCount() != this.blendCount)
//		    throw new SAXParseException("The number of unmapped blend element coordinates must match the number of blends.", this.locator);
//		this.cdata.setLength(0);
//		this.readCData = true;
//		this.split = false;
//	    } else if(qName.equals("Split")) {
//		this.readCData = false;
//	    }
//	}
//
//	public void endElement(String uri, String localName, String qName) throws SAXException  {
//	    this.readCData = false;
//	    if(qName.equals("Map")) {
//		parseIntArray(this.cdata, this.map);
//	    } else if(qName.equals("Split")) {
//		parseFloatArray(this.cdata, this.coords);
//		cdata.setLength(0);
//		this.split = true;
//		this.readCData = true;
//	    } else if(qName.equals("Coordinates")) {
//		if(this.split)
//		    parseIntArray(this.cdata, this.duplicates);
//		else
//		    parseFloatArray(this.cdata, this.coords);
//	    }
//	}
//
//	public void error(SAXParseException e) {
//	    System.out.println("mesh: error: " + e.getMessage());
//	}
//
//	public void fatalError(SAXParseException e) {
//	    System.out.println("mesh: fatalError: "  + e.getMessage());
//	}
//
//	public void warning(SAXParseException e) {
//	    System.out.println("mesh: Warning: "  + e.getMessage());
//	}
//    }
//
//    private static void parseFloatArray(CharSequence floats, float[] values) {
//
//	int numMatches = 0;
//	final Scanner scanner = new Scanner(floats.toString()).useLocale(Locale.ENGLISH);;
//	while(numMatches <values.length) {
//	    values[numMatches++] = scanner.nextFloat();
//	}
//	if(numMatches < values.length)
//	    System.out.println("WARNING !!!!");
//	// TODO: Exception
//    }
//
//    private static void parseIntArray(CharSequence ints, int[] values) {
//
//	int numMatches = 0;
//	final Scanner scanner = new Scanner(ints.toString()).useLocale(Locale.ENGLISH);;
//	while(numMatches <values.length) {
//	    values[numMatches++] = scanner.nextInt();
//	}
//	if(numMatches < values.length)
//	    System.out.println("WARNING !!!!");
//	// TODO: Exception
//    }
//
//    // </editor-fold>
//}
